*! version 1.1.1  16mar2021
*! Sebastian Kripfganz, www.kripfganz.de
*! Jan F. Kiviet, sites.google.com/site/homepagejfk/

*==================================================*
****** Kinky least squares estimation ******

*** citation ***

/*	Kripfganz, S., and J. F. Kiviet. 2021.
	kinkyreg: Instrument-free inference for linear regression models with endogenous regressors.
	Accepted for publication in the Stata Journal.		*/

*** version history at the end of the file ***

program define kinkyreg, eclass
	version 13.0
	if replay() {
		if "`e(cmd)'" != "kinkyreg" {
			error 301
		}
		kinkyreg_parse_display `0'
		loc diopts			`"`s(diopts)'"'
		loc 0				`", `s(options)'"'
		syntax , [CORRelation(numlist max=1 >=-1 <=1)]
		if "`correlation'" != "" {
			if `correlation' < e(grid_min) | `correlation' > e(grid_max) {
				di as err "correlation() invalid -- invalid number, outside of allowed range"
				exit 125
			}
			loc N_grid			= rowsof(e(b_kls))
			forv g = 1 / `N_grid' {
				loc corr			= e(grid_min) + (`g' - 1) * e(grid_step)
				if abs(`correlation' - `corr') <= e(grid_step) / 2 {
					tempname b V
					mat `b'				= e(b_kls)
					mat `b'				= `b'[`g', 1..`= colsof(`b')']
					cap conf mat e(V_`g')
					if _rc {
						error 301
					}
					mat `V'				= e(V_`g')
					eret repost b = `b' V = `V', resize
					eret sca corr		= `corr'
					continue, br
				}
			}
		}
		kinkyreg_display , `diopts'
	}
	else {
		syntax anything(id="varlist" equalok) [if] [in] [, *]
		kinkyreg_parse_display , `options'
		loc diopts			`"`s(diopts)'"'
		loc level			"`s(level)'"
		kinkyreg_init , `s(options)'
		loc mopt			"`s(mopt)'"
		kinkyreg_kls `anything' `if' `in', mopt(`mopt') `level' `s(options)'

		eret loc predict	"kinkyreg_p"
		eret loc estat_cmd	"kinkyreg_estat"
		eret loc cmdline 	`"kinkyreg `0'"'
		eret loc cmd		"kinkyreg"
// 		eret hidden loc mopt	"`mopt'"			// undocumented
		kinkyreg_display , `diopts'
	}
end

program define kinkyreg_kls, eclass
	version 13.0
	syntax anything(id="varlist" equalok) [if] [in] , MOPT(name) [	noCONStant									///
																	CONTrols(varlist num ts fv)					///
																	ENDOgeneity(numlist miss >=-1)				///
																	Range(numlist  min=2 max=2 >=-1 <=1)		///
																	STEPsize(real 0.01)							///
																	EKurtosis(numlist max=1 >=1)				///
																	XKurtosis(numlist max=1 >=1)				///
																	SMall										///
																	INference(varlist num ts fv)				///
																	CORRelation(numlist max=1 >=-1 <=1)			///
																	Level(cilevel)								///
																	NAMEstub(name)								///
																	noGRaph										///
																	noVSTORE									///
																	IVPERfect									///
																	*]											// parsed separately: LINCOM() TWoway() COEFPlot() CIPlot()
	kinkyreg_parse_varlist `anything'
	if `: word count `s(endovars)'' < 1 {
		di as err "too few endogenous variables specified"
		exit 102
	}
	loc 0				`"`s(depvar)' `s(exovars)' `if' `in', endovars(`s(endovars)') ivvars(`s(ivvars)') `options'"'
	syntax varlist(num fv ts) [if] [in], [ENDOvars(varlist num fv ts) IVvars(varlist num fv ts) *]

	loc fv				= ("`s(fvops)'" == "true")
	if `fv' {
		fvexpand `varlist'
		loc varlist			"`r(varlist)'"
	}
	marksample touse
	markout `touse' `endovars' `ivvars'
	gettoken depvar exovars : varlist
	sum `touse', mean
	if r(sum) == 0 {
		error 2000
	}
	if `fv' {
		_fv_check_depvar `depvar'
// 		fvrevar `endovars' `exovars'
	}
	_rmdcoll `depvar' `exovars' if `touse', `constant'
	loc exovars			"`r(varlist)'"
	if "`controls'" != "" {
		_rmdcoll `depvar' `controls' if `touse', `constant'
		loc controls		"`r(varlist)'"
	}
	_rmdcoll `depvar' `endovars' if `touse', `constant'
	if r(k_omitted) {
		di as err "collinearity among endogenous variables"
		exit 459
	}
	foreach endovar in `endovars' {
		_rmdcoll `endovar' `exovars' `controls' if `touse', `constant'
	}
	mata: kinkyreg_init_touse(`mopt', "`touse'")		// marker variable
	tsrevar `depvar'
	mata: kinkyreg_init_depvar(`mopt', "`r(varlist)'")		// dependent variable
	mata: kinkyreg_init_indepvars(`mopt', 1, "`endovars'")		// endogenous independent variables
	mata: kinkyreg_init_indepvars(`mopt', 2, "`exovars'")		// exogenous independent and instrumental variables
	if "`controls'" != "" {
		mata: kinkyreg_init_indepvars(`mopt', 3, "`controls'")		// partialled-out exogenous independent variables
	}
	loc regnames		"`endovars' `exovars' `controls'"
	if "`constant'" != "" {
		mata: kinkyreg_init_cons(`mopt', "off")			// constant term
	}
	else {
		loc regnames		"`regnames' _cons"
	}
	loc K				: word count `regnames'

	*--------------------------------------------------*
	*** endogeneity correlations ***
	loc K_endo			: word count `endovars'
	if "`endogeneity'" != "" {
		if `: word count `endogeneity'' < `K_endo' {
			di as err "endogeneity() invalid -- invalid numlist has too few elements"
			exit 122
		}
		if `: word count `endogeneity'' > `K_endo' {
			di as err "endogeneity() invalid -- invalid numlist has too many elements"
			exit 123
		}
		tempname endocorr
		loc corrmiss		= 0
		forv k = 1 / `K_endo' {
			loc corr			: word `k' of `endogeneity'
			if `corr' > 1 & `corr' < . {
				di as err "endogeneity() invalid -- invalid numlist has elements outside of allowed range"
				exit 125
			}
			if `corr' >= . {
				if `corrmiss' {
					di as err "endogeneity() invalid -- invalid numlist has too many missing values"
					exit 121
				}
				loc klsvar			"`: word `k' of `endovars''"
				loc ++corrmiss
			}
			mat `endocorr'		= (nullmat(`endocorr'), `corr')
		}
		if !`corrmiss' {
			di as err "endogeneity() invalid -- invalid numlist has no missing value"
			exit 121
		}
		mat coln `endocorr'	= `endovars'
		mata: kinkyreg_init_corr(`mopt', st_matrix("`endocorr'"))
	}
	else if `K_endo' > 1 {
		di as err "option endogeneity() required"
		exit 198
	}
	else {
		loc klsvar		"`endovars'"
		mata: kinkyreg_init_corr(`mopt', .)
	}
	if "`range'" != "" {
		gettoken rangemin rangemax : range
		if `rangemax' < `rangemin' {
			di as err "range() invalid -- invalid numlist has elements out of order"
			exit 124
		}
		if `stepsize' <= epsfloat() {
			di as err "stepsize() invalid -- invalid number, outside of allowed range"
			exit 125
		}
	}
	else {
		loc rangemin		= -1
		loc rangemax		= 1
	}
	mata: kinkyreg_init_grid(`mopt', (`rangemin', `stepsize', `rangemax'))
	if "`correlation'" != "" {
		if `correlation' < `rangemin' | `correlation' > `rangemax' {
			di as err "correlation() invalid -- invalid number, outside of allowed range"
			exit 125
		}
	}
	else {
		loc correlation		= .
	}

	*--------------------------------------------------*
	*** linear combinations ***
	kinkyreg_parse_lincom , `options'
	loc options			`"`s(options)'"'
	while `"`s(lincom)'"' != "" {
		loc lincomnums		"`lincomnums' `s(lincomnum)'"
		loc lincom`s(lincomnum)' `"`s(lincom)'"'
		kinkyreg_parse_lincom , `options'
		loc options			`"`s(options)'"'
	}
	if "`lincomnums'" != "" {
		tempname b_lincom C_lincom
		mat `b_lincom'		= J(1, `K', 0)
		mat coln `b_lincom'	= `regnames'
		eret post `b_lincom'
		foreach lincomnum of num `lincomnums' {
			cons free
			loc cons`lincomnum'	= `r(free)'
			cons de `cons`lincomnum'' `lincom`lincomnum'' = 0
			loc consnums		"`consnums' `cons`lincomnum''"
		}
		makecns `consnums', nocnsnote
		if "`r(clist)'" != "`: list retok consnums'" {
			cons drop `consnums''
			di as err "option lincom() incorrectly specified"
			exit 198
		}
		tempname aux1 aux2 C_lincom
		cap matcproc `aux1' `aux2' `C_lincom'
		mata: kinkyreg_init_lincom(`mopt', st_matrix("`C_lincom'"))
		if _rc {
			mat drop `C_lincom'
		}
		else {
			mat drop `aux1' `aux2' `C_lincom'
		}
		cons drop `consnums'
	}

	*--------------------------------------------------*
	*** kurtosis ***
	if "`ekurtosis'" != "" {
		mata: kinkyreg_init_ekurt(`mopt', `ekurtosis')
	}
	if "`xkurtosis'" != "" {
		mata: kinkyreg_init_xkurt(`mopt', `xkurtosis')
	}

	*--------------------------------------------------*
	*** graph options ***
	if "`inference'" == "" & "`lincomnums'" == "" {
		loc inference		"`endovars'"
	}
	else if !`: list inference in regnames' {
		di as err "option inference() incorrectly specified - `: list inference - regnames' not found"
		exit 198
	}
	while `"`options'"' != "" {
		kinkyreg_parse_options `inference' `lincomnums', `options'
		loc optname			"`s(varname)'"
		_ms_parse_parts `optname'
		if "`r(type)'" != "variable" | "`r(op)'" != "" {
			loc optname			: subinstr loc optname "." "_", all
			loc optname			: subinstr loc optname "#" "_", all
		}
		if `"`s(twopt)'"' != "" {
			loc twopt_`optname'	`"`twopt_`optname'' `s(twopt)'"'
			loc order_`optname'	"`s(order)'"
		}
		if `"`s(twname)'"' != "" {
			loc twname_`optname' `"`s(twname)'"'
		}
		if `"`s(addplot)'"' != "" {
			loc addplot_`optname' `"`s(addplot)'"'
			if "`s(before)'" != "" {
				loc addplot_before_`optname' "`s(before)'"
			}
		}
		if "`s(yrange)'" != "" {
			loc twrange_`optname' "`s(yrange)'"
		}
		if `"`s(coefopt)'"' != "" {
			loc coefopt_`s(estimator)'_`optname' `"`coefopt_`s(estimator)'_`optname'' `s(coefopt)'"'
		}
		else if `"`s(ciopt)'"' != "" {
			loc ciopt_`s(estimator)'_`optname' `"`ciopt_`s(estimator)'_`optname'' `s(ciopt)'"'
		}
		loc options			`"`s(options)'"'
	}
	if "`namestub'" == "" {
		loc namestub		"kinkyreg"
	}

	*--------------------------------------------------*
	*** IV estimation ***
	loc K_iv			: word count `ivvars'
	if `K_iv' {
		if "`ivperfect'" != "" {
			loc ivperfect		"perfect"
		}
		tempname b_iv se_iv
		qui ivregress 2sls `depvar' (`endovars' = `ivvars') `exovars' `controls' if `touse', `constant' `ivperfect' `small' l(`level')

		mat `b_iv'			= e(b)
		mat `se_iv'			= vecdiag(e(V))
		mata: st_replacematrix("`se_iv'", sqrt(st_matrix("`se_iv'")))
		tempname mopt_iv
		if "`lincomnums'" != "" {
			foreach lincomnum of num `lincomnums' {
				qui lincom `lincom`lincomnum''
				loc b_iv_lincom`lincomnum' = r(estimate)
				loc se_iv_lincom`lincomnum' = r(se)
			}
		}
	}

	*--------------------------------------------------*
	*** KLS estimation ***
	mata: kinkyreg(`mopt')
	mata: st_numscalar("r(N)", kinkyreg_result_N(`mopt'))
	mata: st_numscalar("r(xkurtosis)", kinkyreg_result_xkurt(`mopt'))
	loc N				= r(N)
	loc xkurtosis		= r(xkurtosis)
	if `correlation' < . {
		tempname b V
	}
	tempname sigma2 ekurtosis b_kls se_kls
	if "`lincomnums'" != "" {
		tempname b_kls_lincom se_kls_lincom
	}
	mata: st_numscalar("r(N_grid)", kinkyreg_result_gridpoints(`mopt'))
	loc N_grid			= r(N_grid)
	loc rank			= 0
	loc miss			= 0
	loc j				= 1
	forv g = 1 / `N_grid' {
		mata: st_numscalar("r(rank)", kinkyreg_result_rank(`mopt', `g'))
		mata: st_numscalar("r(sigma2)", kinkyreg_result_sigma2(`mopt', `g'))
		mata: st_numscalar("r(ekurtosis)", kinkyreg_result_ekurt(`mopt', `g'))
		mata: st_matrix("r(b)", kinkyreg_result_coefs(`mopt', `g'))
		mata: st_matrix("r(V)", kinkyreg_result_V(`mopt', `g'))
		mata: st_matrix("r(corr)", kinkyreg_result_corr(`mopt', `g'))
		if missing(el(r(b), 1, 1)) {
			loc ++miss
			if "`gid'" == "" {
				loc rangemin		= `rangemin' + `stepsize'
			}
			continue
		}
		loc corr			= el(r(corr), 1, 1)
		if missing(`correlation') {
			loc rank			= max(`rank', r(rank))
		}
		else if abs(`correlation' - `corr') <= `stepsize' / 2 {
			loc correlation		= `corr'
			mat `b'				= r(b)
			mat `V'				= r(V)
			loc rank			= r(rank)
			if "`small'" != "" {
				mat `V'				= `N' / (`N' - `rank') * `V'
			}
			mat coln `b'		= `regnames'
			mat rown `V'		= `regnames'
			mat coln `V'		= `regnames'
		}
		if "`vstore'" == "" {
			tempname V`j'
			mat `V`j''			= r(V)
			if "`small'" != "" {
				mat `V`j''			= `N' / (`N' - `rank') * `V`j''
			}
			mat rown `V`j''		= `regnames'
			mat coln `V`j''		= `regnames'
		}
		mat `b_kls'			= (nullmat(`b_kls') \ r(b))
		if "`small'" == "" {
			mat `se_kls'		= (nullmat(`se_kls') \ vecdiag(r(V)))
		}
		else {
			mat `se_kls'		= (nullmat(`se_kls') \ `N' / (`N' - r(rank)) * vecdiag(r(V)))
		}
		if "`lincomnums'" != "" {
			mata: st_matrix("r(b_lincom)", kinkyreg_result_lincom_coefs(`mopt', `g'))
			mata: st_matrix("r(V_lincom)", kinkyreg_result_lincom_V(`mopt', `g'))
			mat `b_kls_lincom'	= (nullmat(`b_kls_lincom') \ r(b_lincom))
			if "`small'" == "" {
				mat `se_kls_lincom'	= (nullmat(`se_kls_lincom') \ vecdiag(r(V_lincom)))
			}
			else {
				mat `se_kls_lincom'	= (nullmat(`se_kls_lincom') \ `N' / (`N' - r(rank)) * vecdiag(r(V_lincom)))
			}
		}
		mat `sigma2'		= (nullmat(`sigma2') \ r(sigma2))
		mat `ekurtosis'		= (nullmat(`ekurtosis') \ r(ekurtosis))
		if c(stata_version) < 14 {
			loc gid				"`gid' `j'"
		}
		else {
			loc gid				"`gid' `: di %5.4f `corr''"
		}
		loc ++j
	}
	loc N_grid			= `N_grid' - `miss'
	loc rangemax		= `corr'
	mata: st_replacematrix("`se_kls'", sqrt(st_matrix("`se_kls'")))
	mat rown `b_kls'	= `gid'
	mat coln `b_kls'	= `regnames'
	mat rown `se_kls'	= `gid'
	mat coln `se_kls'	= `regnames'
	if "`small'" == "" {
		loc critval			= invnormal((100 + `level') / 200)
	}
	else {
		loc critval			= invt(`N' - `rank', (100 + `level') / 200)
	}
	tempname ciub_kls cilb_kls
	mata: st_matrix("`ciub_kls'", st_matrix("`b_kls'") + `critval' * st_matrix("`se_kls'"))
	mata: st_matrix("`cilb_kls'", st_matrix("`b_kls'") - `critval' * st_matrix("`se_kls'"))
	mat rown `ciub_kls'	= `gid'
	mat coln `ciub_kls'	= `regnames'
	mat rown `cilb_kls'	= `gid'
	mat coln `cilb_kls'	= `regnames'
	if "`lincomnums'" != "" {
		mata: st_replacematrix("`se_kls_lincom'", sqrt(st_matrix("`se_kls_lincom'")))
		mat rown `b_kls_lincom' = `gid'
		mat coln `b_kls_lincom' = `lincomnums'
		mat rown `se_kls_lincom' = `gid'
		mat coln `se_kls_lincom' = `lincomnums'
		tempname ciub_kls_lincom cilb_kls_lincom
		mata: st_matrix("`ciub_kls_lincom'", st_matrix("`b_kls_lincom'") + `critval' * st_matrix("`se_kls_lincom'"))
		mata: st_matrix("`cilb_kls_lincom'", st_matrix("`b_kls_lincom'") - `critval' * st_matrix("`se_kls_lincom'"))
		mat rown `ciub_kls_lincom' = `gid'
		mat coln `ciub_kls_lincom' = `lincomnums'
		mat rown `cilb_kls_lincom' = `gid'
		mat coln `cilb_kls_lincom' = `lincomnums'
	}
	mat rown `sigma2'	= `gid'
	mat rown `ekurtosis' = `gid'

	*--------------------------------------------------*
	*** KLS graphs ***
	if "`graph'" == "" {
		tempvar gridrange b1_kls b1_ukls b1_lkls
		qui gen `gridrange' = `rangemin' + (_n - 1) * `stepsize' in 1 / `N_grid'
		la var `gridrange' "postulated endogeneity of `klsvar'"
		qui gen double `b1_kls' = .
		la var `b1_kls' "KLS estimate"
		qui gen double `b1_ukls' = .
		la var `b1_ukls' "KLS >=`level'% CI"
		qui gen double `b1_lkls' = .
		la var `b1_lkls' "KLS >=`level'% CI"
		if "`inference'" != "" {
			foreach var in `inference' {
				loc varpos			: list posof "`var'" in regnames
				loc varname			"`var'"
				_ms_parse_parts `var'
				if "`r(type)'" != "variable" | "`r(op)'" != "" {
					loc var				: subinstr loc var "." "_", all
					loc var				: subinstr loc var "#" "_", all
				}
				if "`twrange_'" != "" & "`twrange_`var''" == "" {
					loc twrange_`var'	"`twrange_'"
				}
				if "`twrange_`var''" != "" {
					loc twif_`var'		"if inrange(`b1_ukls', `twrange_`var'') & inrange(`b1_lkls', `twrange_`var'')"
				}
				mata: st_store((1, `N_grid'), "`b1_kls'", st_matrix("`b_kls'")[., `varpos'])
				mata: st_store((1, `N_grid'), "`b1_ukls'", st_matrix("`ciub_kls'")[., `varpos'])
				mata: st_store((1, `N_grid'), "`b1_lkls'", st_matrix("`cilb_kls'")[., `varpos'])
				loc tw_kls			"line `b1_kls' `gridrange' `twif_`var'', `coefopt_kls_' `coefopt_kls_`var''"
				loc tw_kls_ci		"rarea `b1_ukls' `b1_lkls' `gridrange' `twif_`var'', asty(ci) `ciopt_kls_' `ciopt_kls_`var''"
				if `K_iv' {
					tempvar b1_iv b1_uiv b1_liv
					qui gen double `b1_iv' = el(`b_iv', 1, `varpos') in 1 / `N_grid'
					la var `b1_iv' "IV estimate"
					qui gen double `b1_uiv' = el(`b_iv', 1, `varpos') + `critval' * el(`se_iv', 1, `varpos') in 1 / `N_grid'
					la var `b1_uiv' "IV `level'% CI"
					qui gen double `b1_liv' = el(`b_iv', 1, `varpos') - `critval' * el(`se_iv', 1, `varpos') in 1 / `N_grid'
					la var `b1_liv' "IV `level'% CI"
					if "`twrange_`var''" == "" {
						loc twif_`var'		"if `b1_kls' < ."
					}
					else {
						loc twif_`var'		"if inrange(`b1_uiv', `twrange_`var'') & inrange(`b1_liv', `twrange_`var'') & `b1_kls' < ."
					}
					loc tw_iv			"line `b1_iv' `gridrange' `twif_`var'', `coefopt_iv_' `coefopt_iv_`var''"
					loc tw_iv_ci		"rarea `b1_uiv' `b1_liv' `gridrange' `twif_`var'', asty(ci2) `ciopt_iv_' `ciopt_iv_`var''"
				}
				if `"`twname_`var''"' == "" {
					loc twname_`var'	"name(`namestub'_`var', replace)"
				}
				if "`order_`var''" == "" {
					loc order_`var'		= cond("`order_'" == "", "iv_ci kls_ci iv kls", "`order_'")
				}
				loc tw				""
				foreach twplot in `order_`var'' {
					if "`addplot_before_'" == "`twplot'" | "`addplot_before_`var''" == "`twplot'" {
						loc tw				`"`tw' `addplot_' || `addplot_`var'' ||"'
					}
					loc tw				`"`tw' `tw_`twplot'' ||"'
				}
				if "`addplot_'" != "" & "`addplot_before_'" == "" {
					loc tw				`"`tw' `addplot_' ||"'
				}
				if "`addplot_`var''" != "" & "`addplot_before_`var''" == "" {
					loc tw				`"`tw' `addplot_`var'' ||"'
				}
				tw `tw', yti("`varname' coefficient estimate") `twopt_' `twopt_`var'' `twname_`var''
			}
		}
		if "`lincomnums'" != "" {
			loc j				= 0
			foreach lincomnum of num `lincomnums' {
				loc ++j
				if "`twrange_'" != "" & "`twrange_`lincomnum''" == "" {
					loc twrange_`lincomnum'	"`twrange_'"
				}
				if "`twrange_`lincomnum''" != "" {
					loc twif_`lincomnum'		"if inrange(`b1_ukls', `twrange_`lincomnum'') & inrange(`b1_lkls', `twrange_`lincomnum'')"
				}
				mata: st_store((1, `N_grid'), "`b1_kls'", st_matrix("`b_kls_lincom'")[., `j'])
				mata: st_store((1, `N_grid'), "`b1_ukls'", st_matrix("`ciub_kls_lincom'")[., `j'])
				mata: st_store((1, `N_grid'), "`b1_lkls'", st_matrix("`cilb_kls_lincom'")[., `j'])
				loc tw_kls			"line `b1_kls' `gridrange' `twif_`lincomnum'', `coefopt_kls_' `coefopt_kls_`lincomnum''"
				loc tw_kls_ci		"rarea `b1_ukls' `b1_lkls' `gridrange' `twif_`lincomnum'', asty(ci) `ciopt_kls_' `ciopt_kls_`lincomnum''"
				if `K_iv' {
					tempvar b1_iv b1_uiv b1_liv
					qui gen double `b1_iv' = `b_iv_lincom`lincomnum'' in 1 / `N_grid'
					la var `b1_iv' "IV estimate"
					qui gen double `b1_uiv' = `b_iv_lincom`lincomnum'' + `critval' * `se_iv_lincom`lincomnum'' in 1 / `N_grid'
					la var `b1_uiv' "IV `level'% CI"
					qui gen double `b1_liv' = `b_iv_lincom`lincomnum'' - `critval' * `se_iv_lincom`lincomnum'' in 1 / `N_grid'
					la var `b1_liv' "IV `level'% CI"
					if "`twrange_`lincomnum''" == "" {
						loc twif_`lincomnum'		"if `b1_kls' < ."
					}
					else {
						loc twif_`lincomnum'		"if inrange(`b1_uiv', `twrange_`lincomnum'') & inrange(`b1_liv', `twrange_`lincomnum'') & `b1_kls' < ."
					}
					loc tw_iv			"line `b1_iv' `gridrange' `twif_`lincomnum'', `coefopt_iv_' `coefopt_iv_`lincomnum''"
					loc tw_iv_ci		"rarea `b1_uiv' `b1_liv' `gridrange' `twif_`lincomnum'', asty(ci2) `ciopt_iv_' `ciopt_iv_`lincomnum''"
				}
				if `"`twname_`lincomnum''"' == "" {
					loc twname_`lincomnum'	"name(`namestub'_`lincomnum', replace)"
				}
				if "`order_`lincomnum''" == "" {
					loc order_`lincomnum' = cond("`order_'" == "", "iv_ci kls_ci iv kls", "`order_'")
				}
				loc tw				""
				foreach twplot in `order_`lincomnum'' {
					if "`addplot_before_`lincomnum''" == "`twplot'" {
						loc tw				`"`tw' `addplot_`lincomnum'' ||"'
					}
					loc tw				`"`tw' `tw_`twplot'' ||"'
				}
				if "`addplot_`lincomnum''" != "" & "`addplot_before_`lincomnum''" == "" {
					loc tw				`"`tw' `addplot_`lincomnum'' ||"'
				}
				tw `tw', yti("lincom `lincomnum' estimate") `twopt_' `twopt_`lincomnum'' `twname_`lincomnum''
			}
		}
	}

	*--------------------------------------------------*
	*** current estimation results ***
	if "`small'" != "" {
		loc small			"dof(`= `N' - `rank'')"
	}
	if `fv' {
		loc fvopt			"buildfv"
	}
	if `correlation' < . {
		eret post `b' `V', dep(`depvar') o(`N') `small' e(`touse') `fvopt' findomitted
	}
	else {
		eret post, dep(`depvar') o(`N') `small' e(`touse')
	}
	eret sca rank		= `rank'
	eret sca xkurtosis	= `xkurtosis'
	eret sca grid_min	= `rangemin'
	eret sca grid_max	= `rangemax'
	eret sca grid_step	= `stepsize'
	if `correlation' < . {
		eret sca corr		= `correlation'
	}
	eret loc namestub	"`namestub'"
	if "`constant'" == "" {
		loc controls		"`controls' _cons"
	}
	eret loc ivvars		"`ivvars'"
	eret loc controls	"`: list retok controls'"		// undocumented
	eret loc exovars	"`: list retok exovars'"
	eret loc endovars	"`endovars'"
	eret loc klsvar		"`klsvar'"
	eret mat ekurtosis	= `ekurtosis'
	eret mat sigma2e	= `sigma2'
	if `K_endo' > 1 {
		eret mat endogeneity = `endocorr'
	}
	if "`lincomnums'" != "" {
		eret mat cilb_kls_lincom = `cilb_kls_lincom'
		eret mat ciub_kls_lincom = `ciub_kls_lincom'
		eret mat se_kls_lincom = `se_kls_lincom'
		eret mat b_kls_lincom = `b_kls_lincom'
	}
	eret mat cilb_kls	= `cilb_kls'
	eret mat ciub_kls	= `ciub_kls'
	eret mat se_kls		= `se_kls'
	eret mat b_kls		= `b_kls'

	*--------------------------------------------------*
	*** hidden estimation results ***
	if "`vstore'" == "" {
		forv g = 1 / `N_grid' {
			eret hidden mat V_`g' = `V`g''
		}
	}
end

*==================================================*
**** display of estimation results ****
program define kinkyreg_display
	version 13.0
	syntax [, noOMITted noHEader noTABle *]

	if "`header'" == "" {
		di _n as txt "Kinky least squares estimation" _col(51) as txt "Number of obs" _col(67) "=" _col(69) as res %10.0f e(N)
	}
	if e(corr) < . & "`table'" == "" {
		di _n as txt "Postulated endogeneity of `e(klsvar)' = " %5.4f e(corr)
		_coef_table, `options'
	}
// 	if "`footnote'" == "" {
// 		di as txt "Variables partialled out:"
// 		loc p				= 1
// 		loc piece			: piece 1 77 of "`e(controls)'", nobreak
// 		while "`piece'" != "" {
// 			di _col(2) "`piece'"
// 			loc ++p
// 			loc piece			: piece `p' 77 of "`e(controls)'", nobreak
// 		}
// 	}
end

*==================================================*
**** syntax parsing of additional display options ****
program define kinkyreg_parse_display, sclass
	version 13.0
	sret clear
	syntax , [noHEader noTABle PLus Level(cilevel) *]
	_get_diopts diopts options, level(`level') `options'

	sret loc diopts		`"`header' `table' `plus' `diopts'"'
	sret loc level		"level(`level')"
	sret loc options	`"`options'"'
end

*==================================================*
**** syntax parsing of the optimization options ****
program define kinkyreg_init, sclass
	version 13.0
	sret clear
	syntax [, *]

	loc mopt			"kinkyreg_kls"
	mata: `mopt' = kinkyreg_init()

	sret loc mopt		"`mopt'"
	sret loc options	`"`options'"'
end

*==================================================*
**** syntax parsing of the variable list ****
// (inspired by _iv_parse.ado)
program define kinkyreg_parse_varlist, sclass
	version 13.0
	gettoken depvar 0 : 0, p(" ,[") m(paren) bind

	if inlist(`"`depvar'"', "[", ",", "if", "in") | `"`depvar'"' == "" {
		error 198
	}
	_fv_check_depvar `depvar'
	gettoken var 0 : 0, p(" ,[") m(paren) bind
	while !inlist(`"`var'"', "[", ",", "if", "in") & `"`var'"' != "" {
		if "`paren'" == "(" {
			if `"`endovars'"' != "" | `"`ivvars'"' != "" {
				error 198
			}
			gettoken endovar var : var, parse(" =") bind
			while `"`endovar'"' != "" & `"`endovar'"' != "=" {
				loc endovars		`"`endovars' `endovar'"'
				gettoken endovar var : var, parse(" =") bind
			}
			loc ivvars			`"`var'"'
		}
		else {
			loc exovars			`"`exovars' `var'"'
		}
		gettoken var 0 : 0, p(" ,[") m(paren) bind
	}

	sret loc rest		`"`0'"'
	sret loc ivvars		`"`: list retok ivvars'"'
	sret loc endovars	`"`: list retok endovars'"'
	sret loc exovars	`"`: list retok exovars'"'
	sret loc depvar		`"`depvar'"'
end

*==================================================*
**** syntax parsing of linear combinations ****
program define kinkyreg_parse_lincom, sclass
	version 13.0
	sret clear
	syntax , [LINCOM(string asis) *]

	if `"`lincom'"' != "" {
		gettoken lincomnum lincom : lincom, parse(":")
		cap conf integer n `lincomnum'
		if _rc {
			di as err "option lincom() incorrectly specified"
			exit 198
		}
		if `lincomnum' < 0 {
			di as err "option lincom() incorrectly specified"
			exit 198
		}
		gettoken colon lincom : lincom, parse(":")
		if `"`colon'"' != ":" {
			di as err "option lincom() incorrectly specified"
			exit 198
		}

		sret loc lincomnum	= `lincomnum'
		sret loc lincom		`"`lincom'"'
	}

	sret loc options	`"`options'"'
end

*==================================================*
**** syntax parsing of graph options ****
program define kinkyreg_parse_options, sclass
	version 13.0
	sret clear
	syntax anything(id="plot identifier"), [TWoway(string asis) COEFPlot(string asis) CIPlot(string asis) *]

	if `"`twoway'"' != "" {
		kinkyreg_parse_twoway `twoway'
		loc twvar			`"`s(varname)'"'
		if !`: list twvar in anything' {
			di as err `"option twoway() incorrectly specified -- `twvar' not found"'
			exit 198
		}
		if `"`ciplot'"' != "" {
			loc options			`"ciplot(`ciplot') `options'"'
		}
		if `"`coefplot'"' != "" {
			loc options			`"coefplot(`coefplot') `options'"'
		}
	}
	else {
		if `"`coefplot'"' != "" {
			kinkyreg_parse_coefplot `coefplot'
			loc coefvar			`"`s(varname)'"'
			if !`: list coefvar in anything' {
				di as err `"option coefplot() incorrectly specified -- `coefvar' not found"'
				exit 111
			}
			if `"`ciplot'"' != "" {
				loc options			`"ciplot(`ciplot') `options'"'
			}
		}
		else {
			if `"`ciplot'"' != "" {
				kinkyreg_parse_ciplot `ciplot'
			}
			else if `"`options'"' != "" {
				di as err `"`options' invalid"'
				exit 198
			}
		}
	}

	sret loc options	`"`options'"'
end

*==================================================*
**** syntax parsing of twoway graph options ****
program define kinkyreg_parse_twoway, sclass
	version 13.0
	syntax [anything(id="plot identifier")], [	NAME(passthru)							///
												SAVING(passthru)						///
												ORDER(string)							///
												ADDPLOT(string asis)					///
												YRANGE(numlist miss min=2 max=2)		///
												*]

	gettoken varname anything : anything
	if `"`: list retok anything'"' != "" | (`"`varname'"' == "" & (`"`name'"' != "" | `"`saving'"' != "")) {
		di as err "option twoway() incorrectly specified"
		exit 198
	}
	if "`order'" != "" {
		loc dups			: list dups order
		if "`dups'" != "" {
			di as err "option order() incorrectly specified"
			exit 198
		}
		foreach twplot in `order' {
			if !inlist("`twplot'", "kls", "kls_ci", "iv", "iv_ci") {
				di as err "option order() incorrectly specified"
				exit 198
			}
		}
	}
	if `"`addplot'"' != "" {
		kinkyreg_parse_twoway_addplot `addplot'
	}

	if "`yrange'" != "" {
		loc yrange1			: word 1 of `yrange'
		loc yrange2			: word 2 of `yrange'
		if `yrange1' < . & `yrange1' > `yrange2' {
			di as err "yrange() invalid -- invalid numlist has elements out of order"
			exit 124
		}
		sret loc yrange		"`yrange1', `yrange2'"
	}

	sret loc varname	`"`varname'"'
	sret loc twname		`"`name'"'
	sret loc twopt		`"`saving' `options'"'
	sret loc order		"`order'"
end

*==================================================*
**** syntax parsing of addplot option ****
program define kinkyreg_parse_twoway_addplot, sclass
	version 13.0
	syntax anything(id="graph twoway plot"), [BEFORE(name local) *]

	if "`before'" != "" {
		if !inlist("`before'", "kls", "kls_ci", "iv", "iv_ci") {
			di as err "option before() incorrectly specified"
			exit 198
		}
	}
	if `"`options'"' != "" {
		loc anything		`"`anything', `options'"'
	}

	sret loc before		"`before'"
	sret loc addplot	`"`anything'"'
end

*==================================================*
**** syntax parsing of graph options for coefficient plots ****
program define kinkyreg_parse_coefplot, sclass
	version 13.0
	syntax anything(id="plot identifier"), [*]

	gettoken estimator varname : anything
	if !inlist(`"`estimator'"', "kls", "iv") {
		di as err `"option coefplot() incorrectly specified -- `estimator' invalid"'
		exit 198
	}

	sret loc varname	`"`: list retok varname'"'
	sret loc estimator	"`estimator'"
	sret loc coefopt	`"`options'"'
end

*==================================================*
**** syntax parsing of graph options for confidence interval plots ****
program define kinkyreg_parse_ciplot, sclass
	version 13.0
	syntax anything(id="plot identifier"), [*]

	gettoken estimator varname : anything
	if !inlist(`"`estimator'"', "kls", "iv") {
		di as err `"option ciplot() incorrectly specified -- `estimator' invalid"'
		exit 198
	}

	sret loc varname	`"`: list retok varname'"'
	sret loc estimator	"`estimator'"
	sret loc ciopt		`"`options'"'
end

*==================================================*
*** version history ***
* version 1.1.1  16mar2021  Stata Journal version; improved help files
* version 1.1.0  08feb2021  command kinkyreg2dta added; option ivperfect added; postestimation command estat rcr added; suboption yrange() for postestimation option twoway() added; additional results stored by postestimation commands with option correlation(); bug with option correlation() of estat exclusion fixed; bug with factor variables in estat durbin fixed
* version 1.0.3  22oct2020  bug with option small fixed; confidence intervals and lincom results stored in estimation results
* version 1.0.2  02oct2020  bug with omitted regressors in computation of KLS standard errors fixed; bug with factor variables in postestimation commands fixed
* version 1.0.1  12sep2020  updated reference in the help files
* version 1.0.0  10sep2020  available online at www.kripfganz.de
* version 0.2.0  01sep2020
* version 0.1.5  15aug2020
* version 0.1.4  09jun2020
* version 0.1.3  02jun2020
* version 0.1.2  01jun2020
* version 0.1.1  29may2020
* version 0.1.0  28apr2020
* version 0.0.6  17apr2020
* version 0.0.5  15feb2020
* version 0.0.4  13feb2020
* version 0.0.3  27nov2019
* version 0.0.2  20nov2019
* version 0.0.1  08oct2019
